<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <p>wasd移动</p>
  <canvas id="canvas"></canvas>
  <audio src=""></audio>
  <script>
    const foodImage = new Image();foodImage.src = './food.png';
    const body = new Image();body.src = './body.png';
    const startAudio = document.createElement('audio');startAudio.src = './start.mp3';
    const gameoverAudio = document.createElement('audio');gameoverAudio.src = './gameover.mp3';
    const upHeadImage = new Image();upHeadImage.src = './ht.png';
    const downHeadImage = new Image();downHeadImage.src = './hd.png';
    const leftHeadImage = new Image();leftHeadImage.src = './hl.png';
    const rightHeadImage = new Image();rightHeadImage.src = './hr.png';
 
    const headImageList = {
      up:() => {return upHeadImage;},
      down:() => {return downHeadImage;},
      left:() => {return leftHeadImage;},
      right:() => {return rightHeadImage;}
    }
    class Game{
      constructor(){
        this.gameOver = false;
        this.size = 30;
        this.requestAFID = null;
        this.cvs = document.getElementById('canvas');
        this.ctx = this.cvs.getContext('2d');
        this.X = 20;
        this.Y = 20;
        this.cvs.width = this.X * this.size;
        this.cvs.height = this.Y * this.size;
        this.isBorderLine = false;
        this.snake = [
          {x: 12, y: 10,head:true},
          {x: 11, y: 10},
          {x: 10, y: 10},
          {x: 9, y: 10},
          {x: 8, y: 10},
          {x: 7, y: 10}
        ];
        this.food = {};
        this.isEatFood = true;
        this.direction = 'right';
        this.speed = 100;
        this.init()
      }
      init(){
        this.ctx.clearRect(0,0,this.cvs.width,this.cvs.height);
        this.drawLine();
        this.drawSnake();
        this.addEvent();
        this.render();
        
      }
      move(){
        
        const directions = {
          right: {x: 1, y: 0},
          left: {x: -1, y: 0},
          up: {x: 0, y: -1},
          down: {x: 0, y: 1}
        }
        const val = directions[this.direction];
        const oldPointer = JSON.parse(JSON.stringify(this.snake));
        this.snake[0].x += val.x;
        this.snake[0].y += val.y;
        for(let i = 1; i < this.snake.length; i++){
          this.snake[i].x = oldPointer[i - 1].x;
          this.snake[i].y = oldPointer[i - 1].y;
        }
        this.isHeadToBody();
      }
      drawLine(){
        const {ctx} = this;
        if(!this.isBorderLine){
          ctx.beginPath();
          ctx.moveTo(0,0)
          ctx.lineTo(0, this.cvs.height);
          ctx.lineTo(this.cvs.width, this.cvs.height);
          ctx.lineTo(this.cvs.height, 0);
          ctx.lineTo(0, 0);
          ctx.stroke();
          return;
        }
        for(let i = 0;i < this.X +1 ; i++){
          ctx.beginPath();
          ctx.moveTo(i * this.size, 0);
          ctx.lineTo(i * this.size, this.cvs.height);
          ctx.stroke();
          ctx.closePath();
        }
        for(let j = 0;j < this.Y +1; j++){
          ctx.beginPath();
          ctx.moveTo(0, j * this.size);
          ctx.lineTo(this.cvs.width, j * this.size);
          ctx.stroke();
          ctx.closePath();
        }
      }
      drawSnake(){
        for(let i = 0;i < this.snake.length; i++){
          if(this.snake[i].head){
            this.ctx.drawImage(headImageList[this.direction](), this.snake[i].x * this.size, this.snake[i].y * this.size, this.size, this.size);
          }else{
            this.ctx.beginPath()
            this.ctx.fillStyle = this.randomColor();
            // if(i == this.snake.length){
            //   this.ctx.fillRect(this.snake[i].x * this.size, this.snake[i].y * this.size, this.size, this.size);
            // }else{
            //   this.ctx.fillRect(this.snake[i].x * this.size, this.snake[i].y * this.size, this.size, this.size);
            // }
            
            this.ctx.arc(this.snake[i].x * this.size + this.size / 2, this.snake[i].y * this.size + this.size / 2, this.size / 2, 0, 2 * Math.PI);
            this.ctx.fill();
            this.ctx.closePath();
              // this.ctx.fillRect(this.snake[i].x * this.size, this.snake[i].y * this.size, this.size, this.size);
            // this.ctx.drawImage(body, this.snake[i].x * this.size + 1, this.snake[i].y * this.size + 1, this.size, this.size);
          }
        }
      }
      // 随机颜色
      randomColor(){
        return `rgb(${Math.floor(Math.random() * 256)},${Math.floor(Math.random() * 256)},${Math.floor(Math.random() * 256)})`
      }
      keydownFn = (e) => {
        startAudio.play();
        if(e.keyCode === 65 && this.direction !== 'right'){
          this.direction = 'left';
        }else if(e.keyCode === 87 && this.direction !== 'down'){
          this.direction = 'up';
        }else if(e.keyCode === 68 && this.direction !== 'left'){
          this.direction = 'right';
        }else if(e.keyCode === 83 && this.direction !== 'up'){
          this.direction = 'down';
        }
      }
      addEvent(){
        document.addEventListener('keydown',this.keydownFn);
      }
      draw(){
        this.ctx.clearRect(0,0,this.cvs.width,this.cvs.height);
        this.isBorder();
        this.createFood();
        this.move();
        
        this.drawLine();
        this.drawSnake();
      }
      render = () =>{
        setTimeout(() => {
          if(this.gameOver){
            cancelAnimationFrame(this.requestAFID);
            document.removeEventListener('keydown',this.keydownFn);
            alert('Game Over');
            startAudio.pause();
            gameoverAudio.play();
            return;
          }
          this.draw();
          this.requestAFID = requestAnimationFrame(this.render)
        }, this.speed);
      }
      isBorder(){
        if(this.snake[0].x < 0 || this.snake[0].x >= this.X || this.snake[0].y < 0 || this.snake[0].y >= this.Y){
          this.gameOver = true;
        }
      }
      isHeadToBody(){
        for(let i = 1;i < this.snake.length; i++){
          if(this.snake[0].x === this.snake[i].x && this.snake[0].y === this.snake[i].y){
            this.gameOver = true;
          }
        }
      }
      createFood(){
        this.isEatFoodFn();
        if(this.isEatFood){
          const x = Math.floor(Math.random() * this.X);
          const y = Math.floor(Math.random() * this.Y);
          if(this.snake.some(item => item.x === x && item.y === y)){
            this.createFood();
            return;
          }
          this.food = {x, y};
          this.isEatFood = false;
        }
        this.drawFood();
      }
      drawFood(){
        this.ctx.drawImage(foodImage, this.food.x * this.size, this.food.y * this.size, this.size, this.size);
      }
      isEatFoodFn(){
        if(this.snake[0].x === this.food.x && this.snake[0].y === this.food.y){
          this.isEatFood = true;
          this.addSnakeFoot();
          if(this.snake.length >= 30){
            this.speed -= 1;
          }
        }
      }
      addSnakeFoot(){
        const snake = JSON.parse(JSON.stringify(this.snake));
        const a = snake[snake.length - 1];
        const b = snake[snake.length - 2];
        if(a.x === b.x){
          if(a.y > b.y){
            this.snake.push({x:a.x,y:a.y+1})
          }else{
            this.snake.push({x:a.x,y:a.y-1})
          }
        }else if(a.y === b.y){
          if(a.x > b.x){
            this.snake.push({x:a.x-1,y:a.y})
          }else{
            this.snake.push({x:a.x+1,y:a.y})
          }
        }
      }
 
    }
    let game;
    game = new Game();
  </script>
</body>
</html>